package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.tianji.learning.utils.LearningRecordDelayTaskHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author zjr
 * @since 2024-12-25
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {

    private final ILearningLessonService lessonService;
    private final CourseClient courseClient;
    private final LearningRecordDelayTaskHandler taskHandler;

    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        // 1. 获取当前登录用户
        Long userId = UserContext.getUser();
        // 2. 查询课表信息 条件 user_id course_id
        LearningLesson lesson = lessonService.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        if(lesson == null){
            throw new BizIllegalException("该课程未加入课表");
        }
        // 3. 查询学习记录 条件lesson_id 和 user_id
        List<LearningRecord> recordList = this.lambdaQuery()
                .eq(LearningRecord::getLessonId, lesson.getId())
                .list();
        // 4. 封装结果返回
        LearningLessonDTO dto = new LearningLessonDTO();
        dto.setId(lesson.getId()); // 课表id
        dto.setLatestSectionId(lesson.getLatestSectionId());  // 最近学习的小节id
        List<LearningRecordDTO> dtoList = BeanUtils.copyList(recordList, LearningRecordDTO.class);
        dto.setRecords(dtoList);
        return dto;
    }

    @Override
    public void addLearningRecord(LearningRecordFormDTO dto) {
        // 1. 获取当前登录用户
        Long userId = UserContext.getUser();
        // 2. 处理学习记录
        boolean isFinished = false;  // 代表本小节是第一次学完
        if(dto.getSectionType().equals(SectionType.VIDEO)){
            // 2.1 提交视频播放记录
            isFinished = handleVideoRecord(userId, dto);
        } else {
            // 2.2 提交考试记录
            isFinished = handleExamRecord(userId, dto);
        }
        // 3. 处理课表数据
        if(!isFinished) {  // 如果本小节不是第一次学完，不用处理课表数据
            return;
        }
        handleLessonData(dto);
    }

    /**
     * 处理课表相关数据
     * @param dto 学习记录
     */
    private void handleLessonData(LearningRecordFormDTO dto) {
        // 1. 查询课表 learning_lesson 条件 lesson_id
        LearningLesson lesson = lessonService.getById(dto.getLessonId());
        if(lesson == null){
            throw new BizIllegalException("课表不存在");
        }
        // 2. 判断本小节是否是第一次学完  isFinished
        boolean allFinished = false; // 所有小节是否已学完

        // 3. 远程调用课程服务，得到课程信息  小节总数
        CourseFullInfoDTO cinfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
        if(cinfo == null){
            throw  new BizIllegalException("课程不存在");
        }
        Integer sectionNum = cinfo.getSectionNum();
        // 4. 如果isFinished为true，本小节是第一次学完  判断该用户该课程下全部小节是否学完
        Integer learnedSections = lesson.getLearnedSections();  // 当前用户该课程已学的小节数
        allFinished = learnedSections + 1 >= sectionNum;

        // 5. 更新课表数据
        lessonService.lambdaUpdate()
                .set(lesson.getLearnedSections() == 0, LearningLesson::getStatus, LessonStatus.LEARNING)  // 刚开始学，学习状态由为学习 -> 学习中
                .set(allFinished, LearningLesson::getStatus, LessonStatus.FINISHED)   // 课程学习完，学习状态改为已学完
                .set(LearningLesson::getLatestSectionId, dto.getSectionId())
                .set(LearningLesson::getLatestLearnTime, dto.getCommitTime())
                .setSql("learned_sections = learned_sections + 1")
                .eq(LearningLesson::getId, lesson.getId())
                .update();
    }

    /**
     * 处理视频播放记录
     * @param userId 用户id
     * @param dto 视频播放记录
     * @return 当前小节是否学完（第一次学完才记录为true）
     */
    private boolean handleVideoRecord(Long userId, LearningRecordFormDTO dto) {
        // 1. 查询旧的学习记录 learning_record 条件lesson_id section_id
//        LearningRecord oldRecord = this.lambdaQuery()
//                .eq(LearningRecord::getLessonId, dto.getLessonId())
//                .eq(LearningRecord::getSectionId, dto.getSectionId())
//                .one();
        LearningRecord oldRecord = queryOldRecord(dto.getLessonId(), dto.getSectionId());
        // 2. 判断是否存在
        if(oldRecord == null) {
            // 3. 如果不存在，则新增学习记录
            LearningRecord record = BeanUtils.copyBean(dto, LearningRecord.class);
            record.setUserId(userId);
            boolean result = this.save(record);
            if(!result){
                throw new DbException("新增视频记录失败");
            }
            return false;  // 代表本小节没有学完
        }
        // 4. 如果存在，则更新学习记录 learning_record 更新字段 moment
        // 4.1 判断本小节是否是第一次学完 isFinished为true代表第一次学完
        boolean isFinished = !oldRecord.getFinished() && dto.getMoment() * 2 >= dto.getDuration();
        // 添加学习记录到缓存，执行延迟任务
        if(!isFinished) {
            LearningRecord record = new LearningRecord();
            record.setLessonId(dto.getLessonId());  // 课程id
            record.setSectionId(dto.getSectionId()); // 小节id
            record.setMoment(dto.getMoment());    // 播放时长
            record.setFinished(oldRecord.getFinished());  // 是否学完
            record.setId(oldRecord.getId());        // 学习记录id
            taskHandler.addLearningRecordTask(record);
            return false;
        }

        boolean result = this.lambdaUpdate()
                .set(LearningRecord::getMoment, dto.getMoment())
                .set(LearningRecord::getFinished, true)
                .set(LearningRecord::getFinishTime, dto.getCommitTime())
                .eq(LearningRecord::getId, oldRecord.getId())
                .update();
        if(!result){
            throw new DbException("更新视频学习记录失败");
        }

        // 清理redis缓存
        taskHandler.cleanRecordCache(dto.getLessonId(), dto.getSectionId());
        return true;
    }

    /**
     * 查询学习记录是否已存在
     * @param lessonId 课程id
     * @param sectionId 小节id
     * @return
     */
    private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {
        // 1. 查询缓存
        LearningRecord cache = taskHandler.readRecordCache(lessonId, sectionId);
        // 2. 如果命中，直接返回
        if(cache != null){
            return cache;
        }
        // 3. 如果未命中，查询db
        LearningRecord dbRecord = this.lambdaQuery()
                .eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId)
                .one();
        if(dbRecord == null) {
            // 缓存和db中都没有学习记录，代表这是一次新增记录的操作，直接返回null，走上述函数的新增逻辑
            return null;
        }
        // 4. 将db中数据放入缓存
        taskHandler.writeRecordCache(dbRecord);
        return dbRecord;
    }

    /**
     * 处理考试记录
     * @param userId 用户id
     * @param dto 考试记录
     * @return 当前小节是否学完
     */
    private boolean handleExamRecord(Long userId, LearningRecordFormDTO dto) {
        // 1. 将dto转为po
        LearningRecord record = BeanUtils.copyBean(dto, LearningRecord.class);
        record.setUserId(userId);
        record.setFinished(true); // 提交考试记录，代表本小节已学完
        record.setFinishTime(dto.getCommitTime());
        // 2. 保存学习记录learning_record
        boolean result = this.save(record);
        if(!result){
            throw new DbException("新增考试记录失败");
        }
        return true;
    }
}
